﻿using System.Collections.Generic;
using System.Linq;
using CompleterLib.AbstractTypes;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Util;

namespace CompleterLib
{
    /// <summary>
    /// Wrapper for both real method symbols and operators.
    /// </summary>
    public interface IMethodSymbolWrapper
    {
        IList<TypeWithRefKind> ParameterTypes { get; }
        TypeSymbol ReturnType { get; }
        bool IsStatic { get; }
        string Name { get; }
        // Just the method name (or "new" for a constructor), no type/namespace
        string ShortName { get; }

        string ToString(IEnumerable<string> argStrList);

        TypeTerm GetReturnTypeTerm(AbstractTypeInferencer abstractTypeInferencer);
    }
    internal class MethodSymbolWrapper : IMethodSymbolWrapper
    {
        private readonly MethodSymbol sym;
        private readonly IList<TypeWithRefKind> param;
        private TypeTerm tt;
        public MethodSymbolWrapper(MethodSymbol sym)
        {
            this.sym = sym;
            var paramTemp = sym.Parameters.Select(p => new TypeWithRefKind(p));
            if (!sym.IsStatic && !sym.MethodKind.Equals(MethodKind.Constructor))
            {
                // Add "this" parameter to instance non-constructors
                paramTemp = new TypeWithRefKind(sym.ContainingType).Cons(paramTemp);
            }
            this.param = paramTemp.ToList();
        }

        public MethodSymbol RealSymbol { get { return sym; } }

        public virtual IList<TypeWithRefKind> ParameterTypes
        {
            get { return param; }
        }

        TypeSymbol IMethodSymbolWrapper.ReturnType
        {
            get
            {
                return sym.MethodKind.Equals(MethodKind.Constructor)
                    ? sym.ContainingType
                    : sym.ReturnType;
            }
        }

        public bool IsStatic
        {
            get { return sym.IsStatic || sym.MethodKind.Equals(MethodKind.Constructor); }
        }

        string IMethodSymbolWrapper.Name
        {
            get
            {
                if (sym.MethodKind.Equals(MethodKind.Constructor))
                {
                    return "new " + sym.ContainingType.ToString();
                }
                else
                {
                    string methodName = sym.ToString();
                    methodName = methodName.Substring(0, methodName.IndexOf('('));
                    return methodName;
                }
            }
        }

        string IMethodSymbolWrapper.ShortName
        {
            get
            {
                if (sym.MethodKind.Equals(MethodKind.Constructor))
                {
                    return "new";
                }
                else
                {
                    return sym.Name;
                }
            }
        }

        public virtual string ToString(IEnumerable<string> argStrList)
        {
            var argStrs = argStrList.Zip(param,
                (a, p) => (a.Length == 0 || p.RefKind.Equals(RefKind.None)
                        ? ""
                        : p.RefKind.Equals(RefKind.Out) ? "out " : "ref ")
                    + (a.Length == 0
                        ? "/* " + p.TypeAndName + " */"
                        : a));
            if (sym.MethodKind.Equals(MethodKind.Constructor))
            {
                return "new " + sym.ContainingType.ToString()
                    + "(" + argStrs.CommaSeparated() + ")";
            }
            else if (sym.IsStatic)
            {
                return sym.ContainingType.ToString() + "." + sym.Name
                    + "(" + argStrs.CommaSeparated() + ")";
            }
            else
            {
                return argStrList.First() + "." + sym.Name
                    + "(" + argStrs.Skip(1).CommaSeparated() + ")";
            }
        }

        TypeTerm IMethodSymbolWrapper.GetReturnTypeTerm(AbstractTypeInferencer abstractTypeInferencer)
        {
            if (tt == null)
            {
                tt = abstractTypeInferencer.GetTypeTermFor(sym);
            }
            return tt;
        }

        internal static IMethodSymbolWrapper Create(MethodSymbol methodSymbol, bool hasParent)
        {
            if (hasParent)
            {
                return new MethodSymbolWrapper(methodSymbol);
            }
            else
            {
                return new ImplicitThisMethodSymbolWrapper(methodSymbol);
            }
        }
    }
    internal class ImplicitThisMethodSymbolWrapper : MethodSymbolWrapper
    {
        private readonly IList<TypeWithRefKind> paramTypes;
        public ImplicitThisMethodSymbolWrapper(MethodSymbol sym)
            : base(sym)
        {
            paramTypes = base.ParameterTypes.Skip(1).ToList();
        }

        public override IList<TypeWithRefKind> ParameterTypes
        {
            get { return paramTypes; }
        }

        public override string ToString(IEnumerable<string> argStrList)
        {
            var argStrs = argStrList.Zip(ParameterTypes.Select(p => p.RefKind),
                (a, p) => (a.Length == 0 || p.Equals(RefKind.None)
                    ? ""
                    : p.Equals(RefKind.Out) ? "out " : "ref ") + a);
            return RealSymbol.Name
                    + "(" + argStrs.CommaSeparated() + ")";
        }
    }
    internal class BinaryOpMethodSymbolWrapper : MethodSymbolWrapper
    {
        private readonly string op;

        public BinaryOpMethodSymbolWrapper(MethodSymbol sym, string op)
            : base(sym)
        {
            this.op = op;
        }

        public override string ToString(IEnumerable<string> argStrs)
        {
            var argStrList = argStrs.Take(2).ToList();
            return argStrList[0] + " " + op + " " + argStrList[1];
        }
    }
    internal class BinaryOperatorSymbol : IMethodSymbolWrapper
    {
        private readonly string op;
        private readonly TypeSymbol retType;
        private readonly IList<TypeWithRefKind> param;
        private TypeTerm tt;

        public BinaryOperatorSymbol(string op, TypeSymbol ret,
                                    TypeSymbol left, TypeSymbol right)
        {
            this.op = op;
            this.retType = ret;
            this.param = new List<TypeWithRefKind> { new TypeWithRefKind(left),
                                                     new TypeWithRefKind(right) };
        }
        public BinaryOperatorSymbol(string op, TypeSymbol type)
            : this(op, type, type, type) { }

        IList<TypeWithRefKind> IMethodSymbolWrapper.ParameterTypes
        {
            get { return param; }
        }

        TypeSymbol IMethodSymbolWrapper.ReturnType
        {
            get { return retType; }
        }

        bool IMethodSymbolWrapper.IsStatic
        {
            get { return false; }
        }

        string IMethodSymbolWrapper.Name
        {
            get { return op; }
        }
        string IMethodSymbolWrapper.ShortName
        {
            get { return op; }
        }

        string IMethodSymbolWrapper.ToString(IEnumerable<string> argStrs)
        {
            var argStrList = argStrs.Take(2).ToList();
            return argStrList[0] + " " + op + " " + argStrList[1];
        }

        TypeTerm IMethodSymbolWrapper.GetReturnTypeTerm(AbstractTypeInferencer abstractTypeInferencer)
        {
            // Probably want all binary operator returns to be different type terms.
            // Uh, do we want them even this similar?
            if (tt == null)
            {
                tt = new AnonymousTypeTerm();
            }
            return tt;
        }
    }

}
